/**
 * 请求封装，支持拦截器、基础路径、统一超时时间等，借鉴axios
 * @author luguoxiang
 * @date 2022-03-02
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
import axios from 'axios';
import { Map, toMd5 } from '@/soar';

export interface XhrConfig {
  /**
   * 基础路径
   */
  baseURL?: string;
  /**
   * 路径
   */
  url?: string;
  /**
   * 请求方式
   */
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  /**
   * 请求头
   */
  headers?: Map<any>;
  /**
   * 请求数据
   */
  data?: any;
  /**
   * 请求参数
   */
  params?: {
    /**
     * p包含“-”时会自动包装成查询器语法格式
     * 例：{ 'name-like': '王二小', 'age-ge': 18 } 会转化为 name-like~王二小,age-ge~18
     */
    $soarQuery?: {
      /**
       * 要返回的字段
       */
      fields?: string[] | string;
      /**
       * 要排序的字段
       */
      orderBys?: string[] | string;
      /**
       * 过滤条件，要求直接写查询器格式
       */
      filters?: string[] | string;
      /**
       * p包含“-”时会自动包装成查询器语法格式
       * 例：{ 'name-like': '王二小', 'age-ge': 18 } 会转化为 name-like~王二小,age-ge~18
       */
      [p: string]: any;
    };
    [k: string]: any;
  };
  /**
   * formData数据
   */
  formData?: any;
  /**
   * 超时时间
   */
  timeout?: number;
}

/* ========= 基础实现 ========================================================== */
/**
 * 拦截器
 * @constructor
 */
function Interceptor() {
  this.interceptors = [];
}
Interceptor.prototype = {
  /**
   * 添加拦截器（追加）
   * @param {Function}interceptors 拦截器：resp, config
   */
  add(...interceptors: Function[]) {
    addHandler(interceptors, this.interceptors, 'interceptor must be a function');
  },
  /**
   * 使用拦截器（覆盖、唯一）
   * @param {Function}interceptor
   */
  use(interceptor: Function) {
    this.interceptors = [];
    this.add(interceptor);
  },
};

/**
 * 添加处理器
 * @param source 源
 * @param target 目标
 * @param error 异常
 */
function addHandler(source: any[], target: any[], error = '必须是一个方法') {
  const md5 = target.map((x) => toMd5(x));
  source.forEach((x) => {
    if (typeof x !== 'function') {
      throw new Error(error);
    }
    if (md5.indexOf(toMd5(x)) === -1) {
      target.push(x);
    }
  });
}
/**
 * 请求类实现
 * @param {Object} config
 */
function XhrInstance(config?: XhrConfig) {
  // 不影响外部对象
  config = { ...config };
  // 处理基础路径
  this.baseURL = config.baseURL;
  // 保存配置
  this.config = config;
  this.filters = [];
  this.requestInterceptor = new Interceptor();
  this.responseInterceptor = new Interceptor();
  this.catchInterceptor = new Interceptor();

  delete config.baseURL;
}

// 定义实例方法
XhrInstance.prototype = {
  /**
   * 基础路径
   */
  baseURL: null,
  /**
   * 请求拦截器
   */
  requestInterceptor: new Interceptor(),
  /**
   * 请求响应拦截器
   */
  responseInterceptor: new Interceptor(),
  /**
   * 请求失败拦截器
   */
  catchInterceptor: new Interceptor(),
  /**
   * 添加过滤器（责任链）
   * @param {Function}filters 过滤器
   */
  addFilter(...filters) {
    addHandler(filters, this.filters, 'filter must be a function');
  },
  /**
   * 请求方法
   * @param config 配置
   * @returns {Promise<unknown>}
   */
  request(config: XhrConfig) {
    const call = (cfg) => {
      // 合并参数
      cfg = { ...this.config, ...cfg };
      // 请求拦截器处理
      this.requestInterceptor.interceptors.forEach((x) => {
        const result = x(cfg);
        if (result !== undefined) {
          cfg = result;
        }
      });

      // 处理路径
      if (this.baseURL) {
        cfg.url = this.baseURL + cfg.url;
      }

      // 处理参数
      if (cfg.data && (cfg.method === 'GET' || cfg.method === 'DELETE')) {
        cfg.params = cfg.data;
        delete cfg.data;
      }

      // 处理formData数据
      if (cfg.formData) {
        cfg.data = cfg.formData;
        cfg.headers = { ...cfg.headers, 'Content-Type': 'application/x-www-form-urlencoded' };
        delete cfg.formData;
      }

      // 数组参数省流量处理
      if (cfg.params && !cfg.paramsSerializer) {
        // cfg.paramsSerializer = (params) => stringify(params, { arrayFormat: 'repeat' });
        const params = { ...cfg.params };
        for (const k in params) {
          const list = params[k];
          if (!Array.isArray(list)) {
            continue;
          }
          let flag = true;
          for (const x of list) {
            // 判断是否是复杂数据，复杂数据不支持省流量处理
            if (typeof x === 'object' || (typeof x === 'string' && x.indexOf(',') !== -1)) {
              flag = false;
              break;
            }
          }
          // 转为“,”分隔的字符串进行传输
          if (flag) {
            params[k] = list.join(',');
          }
        }
        cfg.params = params;
      }

      return new Promise((resolve, reject) => {
        // 请求失败处理
        const error = (err) => {
          this.catchInterceptor.interceptors.forEach((x) => {
            const result = x(err, cfg);
            if (result !== undefined) {
              err = result;
            }
          });
          reject(err);
        };
        // 请求成功处理
        const success = (res) => {
          try {
            // 响应拦截
            this.responseInterceptor.interceptors.forEach((x) => {
              const result = x(res, cfg);
              if (result !== undefined) {
                res = result;
              }
            });
            resolve(res);
          } catch (e) {
            error(e);
          }
        };
        // 发起请求
        axios(cfg).then(success).catch(error);
      });
    };
    // 执行过滤器（责任链模式）
    let i = 0;
    const handler = (cfg) => {
      if (i < this.filters.length) {
        return this.filters[i++](config, handler);
      }
      return call(cfg);
    };
    return handler(config);
  },
  /**
   * 发起 get 请求
   * @param url 路径
   * @param config 配置
   * @returns {Promise<unknown>}
   */
  get(url, config?: XhrConfig) {
    return this.request({ ...config, url, method: 'GET' });
  },
  /**
   * 发起 post 请求
   * @param url 路径
   * @param config 配置
   * @returns {Promise<unknown>}
   */
  post(url, config?: XhrConfig) {
    return this.request({ ...config, url, method: 'POST' });
  },
  /**
   * 发起 put 请求
   * @param url 路径
   * @param config 配置
   * @returns {Promise<unknown>}
   */
  put(url, config?: XhrConfig) {
    return this.request({ ...config, url, method: 'PUT' });
  },
  /**
   * 发起 delete 请求
   * @param url 路径
   * @param config 配置
   * @returns {Promise<unknown>}
   */
  delete(url, config?: XhrConfig) {
    return this.request({ ...config, url, method: 'DELETE' });
  },
};

/* ========= 对外提供服务 ====================================================== */

const instance = new XhrInstance();

export default function xhr(config: XhrConfig) {
  return instance.request(config);
}

// 复制实例方法
for (const k in instance) {
  if (typeof instance[k] === 'function') {
    xhr[k] = (...params) => instance[k](...params);
  }
}

/**
 * 创建实例
 */
xhr.create = (config: XhrConfig) => {
  return new XhrInstance(config);
};
